/*
    This file is part of LaTascaDeXela Tactica for JavaCup 2009 (http://javacup.javahispano.org)
    Copyright (C) 2009  Alejandro Guerra Cabrera

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.javahispano.javacup.tacticas_aceptadas.xela.es.latascadexela.javacup;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;

import org.javahispano.javacup.modelo.Comando;
import org.javahispano.javacup.modelo.ComandoGolpearBalon;
import org.javahispano.javacup.modelo.ComandoIrA;
import org.javahispano.javacup.modelo.Constantes;
import org.javahispano.javacup.modelo.JugadorDetalle;
import org.javahispano.javacup.modelo.Posicion;
import org.javahispano.javacup.modelo.SituacionPartido;
import org.javahispano.javacup.modelo.Tactica;
import org.javahispano.javacup.modelo.TacticaDetalle;

/**
 * Implementación de Táctica para la JavaCup 2009
 * 
 * @author Alejandro Guerra Cabrera
 *
 */
public class TacticaLaTascaDeXela implements Tactica {

	// Radio a partir del cual considero que un jugador está marcado
	private static final int RADIO_MARCAJE = 4;

	private static final double RANGO_PASE_RASO = 30;

	private static final double RADIO_ZONA_TIRO = 15;

	final TacticaDetalle detalle = new TacticaLaTascaDeXelaDetalle();

	// Ataque
	Posicion alineacion1[] = new Posicion[] {
			new Posicion(0.2595419847328244, -50.41044776119403),
			new Posicion(-11.412587412587413, -17.34162895927602),
			new Posicion(-0.4755244755244755, -18.529411764705884),
			new Posicion(10.6993006993007, -16.391402714932127),
			new Posicion(-4.041958041958042, 4.038461538461538),
			new Posicion(4.9930069930069925, 14.96606334841629),
			new Posicion(15.216783216783217, 38.72171945701358),
			new Posicion(-15.454545454545453, 38.95927601809955),
			new Posicion(0.0, 30.88235294117647),
			new Posicion(-8.083916083916083, 43.710407239819006),
			new Posicion(9.034965034965035, 43.47285067873303)
			};

	Posicion alineacion2[] = new Posicion[] {
			new Posicion(0.2595419847328244, -50.41044776119403),
			new Posicion(-11.412587412587413, -34.920814479638004),
			new Posicion(-1.4265734265734267, -35.39592760180996),
			new Posicion(8.083916083916083, -34.44570135746606),
			new Posicion(-6.6573426573426575, -24.468325791855204),
			new Posicion(3.5664335664335667, -23.518099547511312),
			new Posicion(19.25874125874126, -29.457013574660635),
			new Posicion(-22.58741258741259, -26.84389140271493),
			new Posicion(-2.377622377622378, -12.115384615384617),
			new Posicion(-11.412587412587413, 2.3755656108597285),
			new Posicion(9.510489510489512, -4.513574660633484) };

	Posicion alineacion3[] = new Posicion[] {
			new Posicion(0.2595419847328244, -50.41044776119403),
			new Posicion(-13.314685314685315, -33.257918552036195),
			new Posicion(-0.4755244755244755, -34.20814479638009),
			new Posicion(14.027972027972028, -34.920814479638004),
			new Posicion(-7.608391608391608, -21.142533936651585),
			new Posicion(4.27972027972028, -20.192307692307693),
			new Posicion(19.97202797202797, -3.800904977375566),
			new Posicion(-21.874125874125873, -9.502262443438914),
			new Posicion(-3.804195804195804, -9.264705882352942),
			new Posicion(-0.7132867132867133, 0.0),
			new Posicion(6.895104895104895, -6.651583710407239) };

	Posicion alineacion4[] = new Posicion[] {
			new Posicion(0.2595419847328244, -50.41044776119403),
			new Posicion(-12.125874125874127, -33.970588235294116),
			new Posicion(0.23776223776223776, -35.15837104072398),
			new Posicion(12.125874125874127, -34.20814479638009),
			new Posicion(-7.608391608391608, -20.90497737556561),
			new Posicion(3.3286713286713288, -24.468325791855204),
			new Posicion(19.496503496503497, -18.766968325791854),
			new Posicion(-21.874125874125873, -20.192307692307693),
			new Posicion(-1.188811188811189, -10.452488687782806),
			new Posicion(-9.986013986013985, -5.226244343891403),
			new Posicion(9.272727272727272, -7.126696832579185) };

	// Ataque alternativo
	Posicion alineacion5[] = new Posicion[] {
			new Posicion(0.2595419847328244, -50.41044776119403),
			new Posicion(-11.412587412587413, -17.34162895927602),
			new Posicion(-0.4755244755244755, -18.529411764705884),
			new Posicion(10.6993006993007, -16.391402714932127),
			new Posicion(-9.510489510489512, 16.628959276018097),
			new Posicion(9.272727272727272, 25.656108597285066),
			new Posicion(26.391608391608393, 32.782805429864254),
			new Posicion(-25.916083916083913, 32.782805429864254),
			new Posicion(-11.412587412587413, 36.34615384615385),
			new Posicion(3.804195804195804, 46.56108597285068),
			new Posicion(-9.510489510489512, 48.699095022624434) };

	// Defendiendo corner
    Posicion alineacion6[]=new Posicion[]{
        new Posicion(0.2595419847328244,-50.41044776119403),
        new Posicion(-6.895104895104895,-46.56108597285068),
        new Posicion(-0.951048951048951,-44.660633484162894),
        new Posicion(6.895104895104895,-47.51131221719457),
        new Posicion(-4.755244755244756,-38.95927601809955),
        new Posicion(2.8531468531468533,-39.19683257918552),
        new Posicion(17.594405594405593,-47.51131221719457),
        new Posicion(-15.93006993006993,-46.08597285067873),
        new Posicion(-1.6643356643356644,-26.368778280542987),
        new Posicion(-7.846153846153847,-5.9389140271493215),
        new Posicion(7.132867132867133,-16.153846153846153)
    };
    
    // Defendiendo con balón cerca de porteria
    Posicion alineacion7[]=new Posicion[]{
            new Posicion(0.2595419847328244,-50.41044776119403),
            new Posicion(-6.895104895104895,-46.56108597285068),
            new Posicion(-0.951048951048951,-44.660633484162894),
            new Posicion(6.895104895104895,-47.51131221719457),
            new Posicion(-4.755244755244756,-38.95927601809955),
            new Posicion(2.8531468531468533,-39.19683257918552),
            new Posicion(12.601398601398602,-41.57239819004525),
            new Posicion(-13.79020979020979,-40.859728506787334),
            new Posicion(-0.23776223776223776,-30.407239819004527),
            new Posicion(-7.846153846153847,-5.9389140271493215),
            new Posicion(7.132867132867133,-16.153846153846153)
        };
    
	public Posicion[] getPosicionSaca(SituacionPartido sp) {
		saqueDeCentro = true;
		return alineacion3;
	}

	public Posicion[] getPosicionRecive(SituacionPartido sp) {
		return alineacion4;
	}

	public TacticaDetalle getDetalle() {
		return detalle;
	}

	int receptorPase = -1;
	int retardoSaque = 0;
	boolean tengoPosesion = false;
	boolean saqueDeCentro = false;
	Posicion posicionAnteriorBalon = null;
	double probabilidadTiroLejano = 0.25;
	boolean hayTiroLejano = false;
	int diffGolesAntesTiroLejano = 0;
	Posicion[] alineacionDefensiva = alineacion2;
	final LinkedList<Comando> comandos = new LinkedList<Comando>();
	final Map<Integer, Posicion[]> posicionAlternativa = new HashMap<Integer, Posicion[]>();
	final Map<Integer, Posicion[]> posicionRegate = new HashMap<Integer, Posicion[]>();
	final Random rand = new Random();
	final List<Integer> siguenBalon = new LinkedList<Integer>();
	
	public List<Comando> ejecutar(SituacionPartido sp) {
		// vacia la lista de comandos
		comandos.clear();

		// reinicio lista de jugadores que siguen el balon
		siguenBalon.clear();
		
		// Actualiza la varible que indica si tengo o no la posesión del balón
		actualizaPosesion(sp);

		// Posiciona jugadores según la tactica
		posicionaJugadoresSegunTactica(sp);

		// Los jugadores indicados van hacia el balón
		posicionaJugadoresSegunBalon(sp);

		// Marcaje a los rivales
		posicionaJugadoresSegunRival(sp);
		
		if(sp.saco() && retardoSaque==0){
			retardoSaque = Constantes.ITERACIONES_SAQUE-1;
		}else if(retardoSaque>0){
			retardoSaque--;
		}
		
		// Calcula los comandos de tiro según la situacion
		calculaGolpeosBalon(sp);			

		if(saqueDeCentro){
			saqueDeCentro=false;
		}
		
		posicionAnteriorBalon = sp.balon();
		
		// retorna la lista de comandos.
		return comandos;
	}

	private void posicionaJugadoresSegunRival(SituacionPartido sp) {
		if(!tengoPosesion && sp.balon().getY()<0){
			int[] marcados = new int[11];
			int contMarcados=0;
			for(int i=1;i<6;i++){
				if(!siguenBalon.contains(i)){
					Posicion jugador = sp.misJugadores()[i];
					int indCercano = jugador.indiceMasCercano(sp.rivales(), marcados);
					Posicion rival = sp.rivales()[indCercano];
					if(estaEnMiArea(rival)){
						marcados[contMarcados++]=indCercano;
						comandos.add(new ComandoIrA(i,new Posicion(rival.getX(),rival.getY()-2)));
					}
				}
			}
		}
	}

	private void calculaGolpeosBalon(SituacionPartido sp) {
		// obtiene un arreglo con los indices de los jugadores que pueden
		// rematar
		int rematan[] = sp.puedenRematar();

		// recorre los jugadores que pueden rematar
		for (int i : rematan) {
			if (receptorPase == i) {
				receptorPase = -1;
			}
			Posicion rematador = sp.misJugadores()[i];
			JugadorDetalle detalleRematador = sp.detalleMisJugadores()[i];
			boolean pase = buscarPase(sp, i, true);

			if (retardoSaque==0) {
				if (estaEnZonaDeTiro(rematador) || tiroLejano(sp)) {
					tirar(sp, i);
				}else if(!pase){
					// si no encontro ningun pase
					Posicion rival = posicionSiguienteRivalMasCercano(sp, rematador);
					if(sp.balon().getY()<-Constantes.LARGO_CAMPO_JUEGO/4 && rematador.distancia(rival)<10){
						comandos.add(new ComandoGolpearBalon(i,new Posicion(Math.signum(sp.balon().getX())*Constantes.ANCHO_CAMPO_JUEGO/2,0),1,Math.PI/3));
					}else{
						Posicion destino;
						if(Math.abs(rematador.getX())>Constantes.LARGO_AREA_CHICA/2 && rematador.getY()<Constantes.LARGO_CAMPO_JUEGO/2-Constantes.ANCHO_AREA_GRANDE){
							// Está en un lateral, avanza por el lateral
							destino = new Posicion(rematador.getX(),Constantes.LARGO_CAMPO_JUEGO/2);
						}else{
							destino = Constantes.centroArcoSup;
						}
						
						if((rival!=null && rematador.distancia(rival)<16)){
							double angRival = rematador.angulo(rival);
							double angDestino = rematador.angulo(destino);
							double dirAng = angDestino-angRival;
							if(Math.abs(dirAng)<Math.PI/3){
								destino = rematador.moverAngulo(angDestino+Math.signum(dirAng)*(Math.PI/3-Math.abs(dirAng)),15);
								if(estaFueraCampo(destino)){
									destino.moverAngulo(Math.PI, 15);
								}
							}
						}
						comandos.add(new ComandoIrA(i, destino));
						double vBalon = Constantes.getVelocidadRemate(detalleRematador.getRemate());
						double vJugador = Constantes.getVelocidad(detalleRematador.getVelocidad());
						int iteraciones = Constantes.ITERACIONES_GOLPEAR_BALON;
						double avanceBalon = calculaAvanceBalon(iteraciones, vBalon,true);
						double avanceJugador = (vJugador*iteraciones)+Constantes.DISTANCIA_CONTROL_BALON;
						double potencia = avanceJugador/avanceBalon;
						comandos.add(new ComandoGolpearBalon(i,destino,Math.min(potencia, 1),false));
					}
				}
			}
			
			if(pase && retardoSaque>0){
				retardoSaque = 0;
			}

		}

		// paro al receptor del pase
		if (receptorPase != -1) {
			comandos.add(new ComandoIrA(receptorPase, sp.balon())); // va en busca del balon
		}
	}
	
	private double calculaAvanceBalon(int iteraciones, double velocidadInicial,boolean suelo){
		// (a -rl)/1-r, l = a*r^n-1
		double a= velocidadInicial;
		double r;
		if(suelo){
			r = Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
		}else{
			r = Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
		}
		double l= a*Math.pow(r, iteraciones);
		return ((a-r*l)/(1-r));
	}

	private boolean tiroLejano(SituacionPartido sp) {
		final boolean tiroLejano;
		double distanciaPorteria = sp.balon().distancia(Constantes.centroArcoSup);
		if(distanciaPorteria<30 && distanciaPorteria>20 && jugadoresEnSuArea(sp)>3 && rand.nextDouble()<probabilidadTiroLejano){
			hayTiroLejano=true;
			diffGolesAntesTiroLejano=sp.golesPropios()-sp.golesRival();
			tiroLejano=true;
		}else{
			tiroLejano=false;
		}
		
		return tiroLejano;
	}

	private int jugadoresEnSuArea(SituacionPartido sp) {
		int jugadoresEnArea = 0;
		int[] indices = Constantes.centroArcoSup.indicesMasCercanos(sp.rivales());
		for(int indice: indices){
			Posicion rival = sp.rivales()[indice];
			if(estaEnAreaContraria(rival)){
				jugadoresEnArea++;
			}
		}
		return jugadoresEnArea;
	}

	private void posicionaJugadoresSegunBalon(SituacionPartido sp) {
		// obtiene el indice del jugador mas cercano al balon
		int indMasCercano = sp.balon().indiceMasCercano(sp.misJugadores());
		// el jugador más cercanos sigue al balon
		seguirBalon(sp, indMasCercano);
		if (!tengoPosesion) {
			// El segundo jugador sólo sigue al balon si no tengo posibilidad de
			// rematar
			// En ese caso me atacan y el que sigue al balón es el jugador más
			// cercano por detrás del balón
			int otrosCercanos[] = sp.balon().indicesMasCercanos(sp.misJugadores(), indMasCercano);
			int encontrados = 0;
			for (int i = 0; encontrados < 1 && i < otrosCercanos.length; i++) {
				int indCercano = otrosCercanos[i];
				Posicion cercano = sp.misJugadores()[indCercano];
				if (sp.detalleMisJugadores()[indCercano].esPortero() 
						|| cercano.getY() < sp.balon().getY()) {
					seguirBalon(sp, indCercano);
					encontrados++;
				}
			}
		}else if(sp.saco()){
			int otroCercano = sp.balon().indiceMasCercano(sp.misJugadores(), indMasCercano);
			Posicion cercano = sp.misJugadores()[otroCercano];
			if(cercano.distancia(sp.balon())>Constantes.DISTANCIA_SAQUE){
				seguirBalon(sp, otroCercano);
			}else if(sp.balon().getY()<Constantes.LARGO_CAMPO_JUEGO/2-Constantes.ANCHO_AREA_CHICA){
				comandos.add(new ComandoIrA(otroCercano,cercano.moverPosicion(0, 10).setDentroCampoJuego()));
			}else{
				comandos.add(new ComandoIrA(otroCercano,cercano.moverPosicion(0, -10).setDentroCampoJuego()));
			}

		}
	}

	private void posicionaJugadoresSegunTactica(SituacionPartido sp) {
		Posicion rivalMasAdelantado = posicionRivalMasAdelantado(sp);
		if (tengoPosesion) {
			// alineacion atacante
			for (int i = 0; i < 11; i++) {
				JugadorDetalle jugador = sp.detalleMisJugadores()[i];
				Posicion posicionJugador = sp.misJugadores()[i];
				if (jugador.getNumero() == 1) {
					posicionAtaque(sp, i);
				} else if (jugador.getNumero() > 1 && jugador.getNumero() <= 4) {
					// defensa
					// siempre detrás del jugador más adelantado
					if (rivalMasAdelantado.getY() > posicionJugador.getY()) {
						posicionAtaque(sp, i);
					}
				} else if (jugador.getNumero() > 4 && jugador.getNumero() <= 6) {
					// centrocampistas
					if (posicionJugador.getY() < sp.balon().getY() + 10) {
						posicionAtaque(sp, i);
					} else {
						// Está mas adelantado, va al balon
						seguirBalon(sp, i);
					}
				} else if (jugador.getNumero() > 6 && jugador.getNumero() <= 8) {
					// extremos
					if (posicionJugador.getY() < sp.balon().getY() + 15) {
						posicionAtaque(sp, i);
					} else {
						// Está demasiado adelantado, se pone a la altura del
						// balon
						comandos.add(new ComandoIrA(i, new Posicion(
								posicionJugador.getX(), sp.balon().getY())));
					}
				} else if (jugador.getNumero() == 9) {
					// mediapunta
					if (posicionJugador.distancia(sp.balon()) < 30) {
						posicionAtaque(sp, i);
					} else {
						// Está demasiado adelantado, va al balon
						seguirBalon(sp, i);
					}
				} else if (jugador.getNumero() > 9) {
					// delanteros
					if (posicionJugador.getY() < sp.balon().getY() + Constantes.LARGO_CAMPO_JUEGO/2) {
						posicionAtaque(sp, i);
					} else {
						// Está demasiado adelantado, se pone a la altura del
						// balon
						comandos.add(new ComandoIrA(i, new Posicion(
								posicionJugador.getX(), sp.balon().getY())));
					}
				}
			}
		} else {
			// alineacion defensiva
			
			for (int i = 0; i < 11; i++) {
				JugadorDetalle jugador = sp.detalleMisJugadores()[i];
				Posicion posicionJugador = sp.misJugadores()[i];
				/*if(sp.balon().getY()<1.5*Constantes.ANCHO_AREA_GRANDE-Constantes.LARGO_CAMPO_JUEGO/2){
					// Marcamos a los jugadores
					if(jugador.getNumero() > 1 && jugador.getNumero() <= 6){
						
					}else{
						comandos.add(new ComandoIrA(i, alineacion6[i]));
					}
				}else{*/
					if (jugador.getNumero() > 1 && jugador.getNumero() <= 4) {
						// defensa
						// siempre detrás del jugador más adelantado
						if (rivalMasAdelantado.getY() > posicionJugador.getY()) {
							comandos.add(new ComandoIrA(i, alineacionDefensiva[i]));
						}else{
							comandos.add(new ComandoIrA(i, new Posicion(alineacionDefensiva[i].getX(),rivalMasAdelantado.getY())));
						}
					}else{
						comandos.add(new ComandoIrA(i, alineacionDefensiva[i]));
					}
//				}
			}

		}
	}

	private boolean buscarPase(SituacionPartido sp, int i, boolean haciaDelante) {
		Posicion rematador = sp.misJugadores()[i];
		// obtiene los jugadores mas cercanos, al jugador que puede rematar
		int[] cercanos = rematador.indicesMasCercanos(sp.misJugadores());
		// encontro un pase en falso
		boolean pase = false;
		// recorre los jugadores desde el mas lejano al mas cercano
		for (int j = 1; !pase && j < cercanos.length; j++) {
			// si el jugador es distinto al jugador que remata y el jugador esta
			// mas adelantado
			Posicion posiblePase = sp.misJugadores()[cercanos[j]];
			if(balonEnMiArea(sp)){
				// Saco a los laterales del area o a larga distancia
				if((Math.abs(posiblePase.getX())>Constantes.LARGO_AREA_GRANDE/2 || posiblePase.getY()>2*Constantes.ANCHO_AREA_GRANDE-Constantes.LARGO_CAMPO_JUEGO/2)
						&& paseViable(sp, rematador, posiblePase, false)){
					calculaPase(sp, i, cercanos[j]);
					pase = true;
				}
			}else if ((sp.saco() || saqueDeCentro || !haciaDelante || esPaseHaciaAdelante(sp,rematador,posiblePase))
					&& rematador.distancia(posiblePase) > 4 
					&& (!sp.saco() || (rematador.getY()>Constantes.LARGO_CAMPO_JUEGO/2-Constantes.ANCHO_AREA_GRANDE 
							|| (rematador.distancia(posiblePase)<Constantes.DISTANCIA_SAQUE && rematador.getY()<posiblePase.getY())))
					//&& !jugadorMarcado(sp, posiblePase)
					&& !estaFueraCampo(posiblePase)
					&& paseViable(sp, rematador, posiblePase,false)) {
				// dar pase al jugador mas cercano que este en posicion mas
				// ofensiva si no está cerca de otro jugador
				calculaPase(sp, i, cercanos[j]);
				// encontro un pase por lo menos
				pase = true;
			}
		}
		return pase;
	}

	private boolean esPaseHaciaAdelante(SituacionPartido sp,
			Posicion rematador, Posicion posiblePase) {
		final boolean haciaAdelante;
		if(rematador.distancia(posiblePase)>RANGO_PASE_RASO){
			// Un pase hacia adelante siempre es raso
			haciaAdelante=false;
		}else{
			Posicion balon = sp.balon();
			if(balon.getY()<0){
				// En mi campo puedo dar pases horizontales
				double angulo = Math.abs(rematador.angulo(posiblePase)-Math.PI/2);
				haciaAdelante = angulo <Math.PI/2.5;
			}else if(balon.getY()<Constantes.LARGO_CAMPO_JUEGO/2-Constantes.ANCHO_AREA_GRANDE){
				// Entre el medio campo y el area grande
				double angulo = Math.abs(rematador.angulo(posiblePase)-Math.PI/2);
				haciaAdelante = angulo <Math.PI/3;
			}else if(Math.abs(balon.getX())>Constantes.LARGO_AREA_GRANDE/2) {
				// En el lateral del area grande
				haciaAdelante = estaEnAreaContraria(posiblePase);
			}else{
				// En el area grande
				haciaAdelante = estaEnZonaDeTiro(posiblePase);
			}
		}
		return haciaAdelante;
	} 

	private boolean estaEnZonaDeTiro(Posicion posicion){		
		return (posicion.getY()> Constantes.LARGO_CAMPO_JUEGO/2-RADIO_ZONA_TIRO) // Está más alla del punto de penalty en y
		&& (Math.abs(posicion.getX())<(Constantes.LARGO_AREA_GRANDE+Constantes.LARGO_AREA_CHICA)/2)
		&& (posicion.getY()<Constantes.LARGO_CAMPO_JUEGO/2-Constantes.ANCHO_AREA_CHICA/2 || Math.abs(posicion.getX())<Constantes.LARGO_AREA_CHICA/2)
		&& posicion.distancia(Constantes.centroArcoSup)<RADIO_ZONA_TIRO;
	}
	
	private boolean paseViable(SituacionPartido sp, Posicion rematador,
			Posicion posiblePase, boolean excluirPortero) {
		boolean viable = true;
		final double dPase = rematador.distancia(posiblePase);
		final double dLimite;
		if(dPase<=RANGO_PASE_RASO){
			dLimite=dPase+4;
		}else{
			dLimite=8;
		}
			
		// Si es un pase raso calculo la viabilidad
		final int[] indRivalesCercanos = rematador.indicesMasCercanos(sp.rivales());
		boolean fin = false;
		double limite=sp.balon().getY()<-Constantes.LARGO_CAMPO_JUEGO/3?3:(estaEnZonaDeTiro(posiblePase)?1.2:2);
		for(int i=0;i<indRivalesCercanos.length && !fin && viable;i++){
			final Posicion rival = sp.rivales()[indRivalesCercanos[i]];
			// El resto estarán demasiado alejados, no sigo
			if(rematador.distancia(rival)>dLimite){
				fin = true;
			}else{
				final JugadorDetalle detalleRival = sp.detalleJugadoresRivales()[indRivalesCercanos[i]];
				if((!excluirPortero || !detalleRival.esPortero()) && rival.distancia(posiblePase)<dPase){
					// Calculo la distancia del rival a la recta que une rematador y posiblePase
					double difX = posiblePase.getX()-rematador.getX();
					double difY = posiblePase.getY()-rematador.getY();
					double dist = (difX*(rival.getY()-rematador.getY())-difY*(rival.getX()-rematador.getX()))/(Math.sqrt(difX*difX+difY*difY));
					if(Math.abs(dist)<limite){
						viable=false;
					}
				}
			}
		}
		
		return viable;
		
	}

	private void calculaPase(SituacionPartido sp, int indiceRematador, int indiceReceptor){
		JugadorDetalle rematador = sp.detalleMisJugadores()[indiceRematador];
		Posicion posRematador = sp.misJugadores()[indiceRematador];
		Posicion receptor = sp.misJugadores()[indiceReceptor];
		double distancia = posRematador.distancia(receptor);
		boolean alto;
		double coefFuerza;
		if (distancia > RANGO_PASE_RASO) {
			alto = true;
			coefFuerza = 0.7*1/rematador.getRemate();
		} else {
			alto = false;
			coefFuerza = 1;
		}
		double fuerza = Math.min(coefFuerza*(distancia/30) * (1.4/rematador.getRemate()), 1);
		//System.out.println("Pase([d,r,f]) ["+distancia+","+rematador.getRemate()+","+fuerza+"]");
		comandos.add(new ComandoGolpearBalon(indiceRematador, receptor, fuerza, alto));
		// marco al receptor del pase
		receptorPase = indiceReceptor;
	}
	
	private boolean balonEnMiArea(SituacionPartido sp){
		return estaEnMiArea(sp.balon());
	}
	
	private boolean estaEnMiArea(Posicion posicion){
		return posicion.getY()<Constantes.ANCHO_AREA_GRANDE-Constantes.LARGO_CAMPO_JUEGO/2 && Math.abs(posicion.getX())<Constantes.LARGO_AREA_GRANDE/2;	
	}
	
	private boolean estaEnAreaContraria(Posicion posicion){
		return Math.abs(posicion.getX())<Constantes.LARGO_AREA_GRANDE/2 && posicion.getY()>Constantes.LARGO_CAMPO_JUEGO/2-Constantes.ANCHO_AREA_GRANDE*1.5;
	}
	
	private void posicionAtaque(SituacionPartido sp, int i) {
		Posicion posicionJugador = sp.misJugadores()[i];
		Posicion posicionAtaque = getPosicionAtaque(i);
		if (!posicionJugador.equals(posicionAtaque)) {
			comandos.add(new ComandoIrA(i, posicionAtaque));
		} else if(jugadorMarcado(sp, posicionJugador)){
			// Ha llegado a la posición objetivo
			// Está marcado -> cambia a posicion alternativa
			cambiaPosicionAtaque(i);
			comandos.add(new ComandoIrA(i, getPosicionAtaque(i)));
		}
	}
	
	private void cambiaPosicionAtaque(int i) {
		Posicion[] posicion = posicionAlternativa.get(i);
		if(posicion==null){
			posicionAlternativa.put(i, alineacion5);
		}else{
			posicionAlternativa.remove(i);
		}
	}

	private Posicion getPosicionAtaque(int i){
		Posicion[] posicion = posicionAlternativa.get(i);
		if(posicion==null){
			posicion = alineacion1;
		}
		
		return posicion[i];
	}

	private void actualizaPosesion(SituacionPartido sp) {
		int maximoMio = calculaMaximo(sp.iteracionesParaRematar());
		int maximoRival = calculaMaximo(sp.iteracionesParaRematarRival());
		boolean teniaPosesion = tengoPosesion;
		if (sp.saco() || maximoMio > maximoRival) {
			tengoPosesion = true;
		} else if (sp.sacaRival() || maximoMio < maximoRival) {
			tengoPosesion = false;
		}
		
		if(sp.balon().getY()>1.5*Constantes.ANCHO_AREA_GRANDE-Constantes.LARGO_CAMPO_JUEGO/2){
			alineacionDefensiva=alineacion2;
		}else if (teniaPosesion && !teniaPosesion && sp.sacaRival() && sp.balon().getY()<Constantes.ANCHO_AREA_GRANDE-Constantes.LARGO_CAMPO_JUEGO/2){
			alineacionDefensiva=alineacion6;
		}else{
			alineacionDefensiva=alineacion7;
		}
		
		// Actualiza probabilidad tiro lejano
		if(teniaPosesion && !tengoPosesion && hayTiroLejano){
			// Fin de tiro lejano
			hayTiroLejano=false;
			int diffGolesActual= sp.golesPropios()-sp.golesRival();
			if(diffGolesActual>diffGolesAntesTiroLejano){
				// He marcado -> aumento probabilidad
				probabilidadTiroLejano+=0.1;
			}else{
				// No he marcado -> disminuyo probabilidad
				probabilidadTiroLejano-=0.05;
			}
		}

	}

	private int calculaMaximo(int[] array) {
		int maximo = 0;
		for (int actual : array) {
			if (actual > maximo) {
				maximo = actual;
			}
		}
		return maximo;
	}

	private boolean jugadorContrarioCerca(SituacionPartido sp,
			Posicion posibleRematador, int umbral) {
		Posicion rivalMasCercano = sp.rivales()[posibleRematador.indiceMasCercano(sp.rivales())];
		return posibleRematador.distancia(rivalMasCercano) < umbral;
	}

	private boolean jugadorMarcado(SituacionPartido sp,
			Posicion posibleRematador) {
		return jugadorContrarioCerca(sp, posibleRematador, RADIO_MARCAJE);
	}

	private void tirar(SituacionPartido sp, int indiceRematador) {
		Posicion posRematador = sp.misJugadores()[indiceRematador];
		// Calculamos objetivo del tiro
		double distancia = posRematador.distancia(Constantes.centroArcoSup);
		if (distancia > 20) {
			JugadorDetalle detalleRematador = sp.detalleMisJugadores()[indiceRematador];
			comandos.add(new ComandoGolpearBalon(indiceRematador, Constantes.centroArcoSup,	Math.min(distancia * 0.045/detalleRematador.getRemate(), 1), true));
		} else {
			Posicion objetivoTiro = calculaPosicionTiro(sp, indiceRematador);
			comandos.add(new ComandoGolpearBalon(indiceRematador, objetivoTiro,	1, false));
		}
	}

	private Posicion calculaPosicionTiro(SituacionPartido sp, int indRematador) {
		final Posicion objetivo;

		final Posicion rematador = sp.misJugadores()[indRematador];
		final Posicion portero = sp.rivales()[buscaPortero(sp
				.detalleJugadoresRivales())];

		double anguloParteDcha = calcularAnguloVision(rematador,
				Constantes.posteDerArcoSup, portero);
		double anguloParteIzda = calcularAnguloVision(rematador, portero,
				Constantes.posteIzqArcoSup);

		if (anguloParteDcha < 0 || anguloParteIzda < 0) {
			// El portero no cubre la porteria, tiro al centro
			objetivo = Constantes.centroArcoSup;
		} else {
			final Posicion proyPortero = Posicion.Interseccion(rematador,
					portero, Constantes.posteDerArcoSup,
					Constantes.posteIzqArcoSup);
			final Posicion posteObjetivo;
			if (anguloParteDcha > anguloParteIzda) {
				// Tiro a la derecha
				posteObjetivo = Constantes.posteDerArcoSup;
			} else {
				// Tiro a la izquierda
				posteObjetivo = Constantes.posteIzqArcoSup;
			}
			objetivo = Posicion.media(proyPortero.moverPosicion(Math.signum(posteObjetivo.getX()) * proyPortero.distancia(posteObjetivo) / 2, 0),
					posteObjetivo);
		}

		return objetivo;

	}

	private void seguirBalon(SituacionPartido sp, int indiceJugador) {
		boolean esPortero = sp.detalleMisJugadores()[indiceJugador].esPortero();
		if (esPortero && sp.alturaBalon() > 0) {
			if(posicionAnteriorBalon!=null){
				Posicion objetivoTiro = Posicion.Interseccion(posicionAnteriorBalon, sp.balon(), Constantes.cornerInfDer, Constantes.cornerInfIzq);
				if(objetivoTiro!=null && Math.abs(objetivoTiro.getX())<1+Constantes.LARGO_ARCO/2){
					comandos.add(new ComandoIrA(indiceJugador, objetivoTiro));
				}
			}
		} else if(!esPortero || balonEnMiArea(sp)){
			Posicion objetivo = sp.balon();
			if(posicionAnteriorBalon!=null && !tengoPosesion){
				double dirBalon = posicionAnteriorBalon.angulo(sp.balon());
				objetivo= sp.balon().moverAngulo(dirBalon, 0.9);
			}
			comandos.add(new ComandoIrA(indiceJugador, objetivo));
		}
		siguenBalon.add(indiceJugador);
		
	}

	private double calcularAnguloVision(Posicion posBase, Posicion punto1,
			Posicion punto2) {
		double angPend01 = posBase.angulo(punto1);
		double angPend02 = posBase.angulo(punto2);
		return angPend02 - angPend01;

	}

	private int buscaPortero(JugadorDetalle jugadores[]) {
		int indPortero = -1;
		for (int i = 0; indPortero == -1 && i < jugadores.length; i++) {
			JugadorDetalle jugador = jugadores[i];
			if (jugador.esPortero()) {
				indPortero = i;
			}
		}
		return indPortero;
	}

	private Posicion posicionRivalMasAdelantado(SituacionPartido sp) {
		Posicion masAdelantado = new Posicion(0, 0);
		for (Posicion posicion : sp.rivales()) {
			if (posicion.getY() < masAdelantado.getY()) {
				masAdelantado = posicion;
			}
		}
		return masAdelantado;
	}
	
	private Posicion posicionSiguienteRivalMasCercano(SituacionPartido sp, Posicion referencia) {
		int[] cercanos = referencia.indicesMasCercanos(sp.rivales());
		Posicion siguienteRival = null;
		boolean encontrado = false;
		for(int i=0;i<cercanos.length && !encontrado;i++){
			Posicion rival = sp.rivales()[cercanos[i]];
			//if(Math.abs(referencia.angulo(rival)-Math.PI/2)<Math.PI/3){
			if(referencia.getY()<rival.getY()){
				siguienteRival = rival;
				encontrado = true;
			}
		}

		return siguienteRival;
	}
	
	private boolean estaFueraCampo(Posicion posicion){
		return Math.abs(posicion.getX())>Constantes.ANCHO_CAMPO_JUEGO/2 || Math.abs(posicion.getY())>Constantes.LARGO_CAMPO_JUEGO/2;
	}
}